﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Brain
{
    public class ChaseCamera3D : ICamera3D, IBaseComponent, IDrawable
    {
        public float NearPlane { get; set; }
        public float FarPlane { get; set; }

        public Matrix View { get; set; }
        public Matrix Projection { get; set; }

        public Vector3 Position { get; set; }
        public Vector3 Target { get; set; }

        public Vector3 Up { get; set; }

        public BoundingFrustum Frustum { get; set; }

        public string Name { get; set; }

        public bool GetAspectRatioFromEngineViewport { get; set; }

        public bool ShouldDraw { get; set; }

        public float AspectRatio { get; set; }

        public float FieldOfView { get; set; }

        public Vector3 ToFollowPosition { get; set; }
        public Matrix ToFollowMatrix { get; set; }
        public Vector3 ToFollowPositionOffset { get; set; }
        public bool TransformPositionOffset { get; set; }

        public float CurrentDistance = 25;

        /// <summary>
        /// If true, enables Ray Testing on the Physics Manager 3D
        /// (if present) to adjust the position to the
        /// best place behind the follow position
        /// </summary>
        public bool EnableRayTesting = true;

        /// <summary>
        /// The object to exclude from Ray Testing,
        /// when on Physics
        /// </summary>
        public WorldObject ExcludeObject { get; set; }

        public WorldObject[] ExcludeObjects { get; set; }

        public ChaseCamera3D()
        {
            ShouldDraw = true;
            GetAspectRatioFromEngineViewport = true;
            NearPlane = 0.1f;
            FarPlane = 1000f;

            FieldOfView = MathHelper.PiOver4;

            Frustum = new BoundingFrustum(Matrix.Identity);
        }
        public ChaseCamera3D(GameScreen gs)
            : this()
        {
            gs.AddComponent(this);
        }

        public void UpdateStatus(WorldObject toFollow)
        {
            ToFollowPosition = toFollow.Position;
            ToFollowMatrix = toFollow.RotationMatrix;
        }

        public void Draw()
        {
            Vector3 camup = new Vector3(0, 1, 0);
            camup = Vector3.Transform(camup, this.ToFollowMatrix);
            this.Up = camup;

            Vector3 finalTargetOffset;
            if (TransformPositionOffset)
            {
                finalTargetOffset = Vector3.Transform(ToFollowPositionOffset, ToFollowMatrix);
            }
            else
            {
                finalTargetOffset = ToFollowPositionOffset;
            }
            this.Target = this.ToFollowPosition + finalTargetOffset;

            Vector3 Backward = ToFollowMatrix.Backward;
            Vector3 pos = Target + (CurrentDistance * Backward);
            float dist = Vector3.Distance(pos, ToFollowPosition);

            // Physics Camera -> Tests if is there stuff in front of the camera, so it won't get inside any physic object
            if (Engine.Instance.Physics3DManager != null && EnableRayTesting)
            {
                RayTestData rayData;
                if (ExcludeObject != null && Engine.Instance.Physics3DManager.RayTest(new Ray(Target, Backward), dist, out rayData, ExcludeObject) ||
                    ExcludeObjects != null && Engine.Instance.Physics3DManager.RayTest(new Ray(Target, Backward), dist, out rayData, ExcludeObjects) ||
                    ExcludeObjects == null && ExcludeObject == null && Engine.Instance.Physics3DManager.RayTest(new Ray(Target, Backward), out rayData))
                {
                    Position = Target + (rayData.T) * Backward; //Put the camera just before any hit
                }
                else
                {
                    Position = Target + (CurrentDistance * Backward);
                }
            }
            else
            {
                Position = Target + (CurrentDistance * Backward);
            }

            float aspect = this.AspectRatio;
            if (GetAspectRatioFromEngineViewport)
            {
                aspect = Engine.Instance.GraphicsDevice.Viewport.AspectRatio;
            }

            this.View = Matrix.CreateLookAt(Position, Target, camup);
            Projection = CameraUtil.GetProjectionMatrix(this.FieldOfView,
                aspect, this.NearPlane, this.FarPlane);
            Frustum.Matrix = View * Projection;
        }

        public bool ShouldSerialize { get; set; }

        public void SerializeData(SerializationData parentData, string index)
        {
        }

        public void DeserializeData(SerializationData parentData, string index)
        {
        }


        public void DeserializedAll()
        {
        }
    }
}
